package com.sunday.common.statemachine.study.e3_Choice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;

import java.util.EnumSet;

/**
 * 选择状态
 * 需要在 state 和 transition to work 中定义 choice 适当地。您可以使用该方法将特定状态标记为选择状态。当转换 配置。choice()
 * <p>
 * 您可以使用 配置过渡，其中定义了源 state 和一个结构，它相当于普通的 .使用 和 ，你可以指定一个守卫 就像您将使用带子句的条件一样。
 * <p>
 * withChoice()
 * first/then/last  =  if/elseif/else
 * first = if
 * then = elseif
 * last = else
 * <p>
 * 过渡需要能够存在，因此您必须确保使用 . 否则，配置格式不正确。以下示例显示了如何定义 a choice 状态：last
 */

@Slf4j
@Configuration  // 声明该类为Spring的配置类
@EnableStateMachine  // 启用Spring状态机功能
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener())  // 原有监听器
//                .interceptor(stateInterceptor())  // 添加拦截器
        ;
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        states
                .withStates()
                .initial(OrderStates.NEW)
                .choice(OrderStates.CHECKED)
                .state(OrderStates.BACKWASH)
                .state(OrderStates.PAID)
                .state(OrderStates.SHIPPED)
                .end(OrderStates.COMPLETED)
                .end(OrderStates.CANCELED)
                .states(EnumSet.allOf(OrderStates.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.NEW)  // 源状态：NEW
                .target(OrderStates.CHECKED)  // 目标状态：CHECKED
                .event(OrderEvents.CHECK_STOCK)  // 触发事件：CHECK_STOCK（检查库存事件）
                .action(orderCreatedAction())
                .and()  // 结束当前转换定义，开始下一个转换规则

                /*
                 * 经过测试可以发现，当不存在 .event 的时候，流程会自动流转
                 *
                 * 在 Spring StateMachine 中，当状态机进入 choice（选择状态）时，stateChanged 的监听不会触发，
                 * 因为 choice 状态是一个过渡状态，它不会被视为一个真正的持久状态。
                 * choice 的设计是为了快速决策并转移到下一个持久状态，而不在 choice 本身停留。
                 *
                 * 原因分析：
                 * choice 状态的特性：
                 * choice 状态不会被认为是一个实际的状态，而是一个逻辑判断节点。因此不会触发 stateChanged 监听方法。
                 *
                 * stateChanged 的触发条件：
                 * 只有当状态机进入或退出一个真正的状态时（即不是过渡状态如 choice），stateChanged 才会被调用。
                 */
                // choice 状态分支
                .withChoice()
                .source(OrderStates.CHECKED)
                .first(OrderStates.PAID, inventoryAvailableGuard(), paymentAction()) // 库存充足 -> 支付状态
                .last(OrderStates.BACKWASH, cancelAction()) // 库存不足 -> 取消订单
                .and()

                .withExternal()
                .source(OrderStates.BACKWASH)  // 源状态：PAID
                .target(OrderStates.CANCELED)  // 目标状态：SHIPPED
                .action(backwashAction())
                .action(cancelCompleteAction())
                .and()  // 结束当前转换定义，开始下一个转换规则

                .withExternal()
                .source(OrderStates.PAID)  // 源状态：PAID
                .target(OrderStates.SHIPPED)  // 目标状态：SHIPPED
                .event(OrderEvents.SHIP)  // 触发事件：SHIP（发货事件）
                .action(paymentSuccessAction())
                .and()  // 结束当前转换定义，开始下一个转换规则

                .withExternal()
                .source(OrderStates.SHIPPED)  // 源状态：SHIPPED
                .target(OrderStates.COMPLETED)  // 目标状态：COMPLETED
                .event(OrderEvents.COMPLETE)  // 触发事件：COMPLETE（订单完成事件）
                .action(completeAction())
                .and()


                // 新增订单取消的状态转换配置，从不同状态都可以触发取消订单事件，转换到已取消状态
                .withExternal()
                .source(OrderStates.NEW) // 源状态可以是新建订单状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelCompleteAction())
                .and()
                .withExternal()
                .source(OrderStates.PAID) // 源状态也可以是已支付状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelCompleteAction())
                .and()
                .withExternal()
                .source(OrderStates.SHIPPED) // 源状态还可以是已发货状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelCompleteAction())
        ;
    }

    // Guard 判断库存是否充足
    @Bean
    public Guard<OrderStates, OrderEvents> inventoryAvailableGuard() {
        return context -> {
            // 模拟库存判断
//            Boolean stockAvailable = (Boolean) context.getExtendedState().getVariables().getOrDefault("stockAvailable", false);
            Boolean stockAvailable = (Boolean) context.getMessageHeaders().getOrDefault("stockAvailable", false);
            return stockAvailable;
        };
    }

    // 订单创建时的动作
    @Bean
    public Action<OrderStates, OrderEvents> orderCreatedAction() {
        return context -> log.info("订单已创建，开始检查库存...");
    }

    // 支付时的动作
    @Bean
    public Action<OrderStates, OrderEvents> paymentAction() {
        return context -> log.info("库存充足，进入支付流程...");
    }

    // 取消时的动作
    @Bean
    public Action<OrderStates, OrderEvents> cancelAction() {
        return context -> log.info("库存不足，订单已取消...");
    }

    // 支付成功后的动作
    @Bean
    public Action<OrderStates, OrderEvents> paymentSuccessAction() {
        return context -> log.info("支付成功，订单已完成！");
    }

    @Bean
    public Action<OrderStates, OrderEvents> cancelCompleteAction() {
        return context -> log.info("订单取消流程完成！");
    }

    @Bean
    public Action<OrderStates, OrderEvents> completeAction() {
        return context -> log.info("订单完成流程完成！");
    }


    // 回冲库存流程完成
    @Bean
    public Action<OrderStates, OrderEvents> backwashAction() {
        return context -> log.info("库存回冲，订单已取消...");
    }

    /**
     * 关键点：
     * StateMachineListener: 用来监听状态变更，包括每个状态的进入和退出，甚至是每个状态之间的过渡（transition）。stateChanged 方法可以捕获状态的变化，transition 方法捕获状态转移。
     * <p>
     * StateMachineConfigurationConfigurer: 配置中通过 withConfiguration() 方法来启用监听器。
     * <p>
     * Action: 可以通过 StateMachineTransitionConfigurer 的 withExternal() 来配置每个状态转移时执行的动作（如支付和发货）。
     * <p>
     * stateMachineListener: 我们为 StateMachineListener 提供了一个 StateMachineListenerAdapter 的实现，该实现会覆盖 stateChanged 和 transition 方法，在这些方法中添加日志或其他监控操作。
     * <p>
     * 监控状态变更
     * stateChanged：监听状态变化的事件，适用于我们关注的状态变更（如从 NEW 到 PAID）。
     * transition：监听状态过渡的事件，可以捕获所有的状态变化，包括 choice 选择。
     */
    @Bean
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<>() {
            // 监听状态过渡的事件，可以捕获所有的状态变化，包括 choice 选择。
            @Override
            public void transition(Transition<OrderStates, OrderEvents> transition) {
                if (transition.getSource() != null && transition.getTarget() != null) {
                    log.info("Transition from {} to {}",
                            transition.getSource().getId(),
                            transition.getTarget().getId());
                }
            }

            // 监听状态变化的事件，适用于我们关注的状态变更（如从 NEW 到 PAID）。
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("State changed from {} to {}",
                        from != null ? from.getId() : "无",
                        to != null ? to.getId() : "无");
            }
        };
    }

}